﻿using JESAI.DistributedLock.Internal;
using JESAI.DistributedLock.Redis.RedLock;
using System;

namespace JESAI.DistributedLock.Redis;

/// <summary>
/// 用于配置基于Redis的分布式同步算法的选项
/// </summary>
public sealed class RedisDistributedSynchronizationOptionsBuilder
{
    internal static readonly TimeoutValue DefaultExpiry = TimeSpan.FromSeconds(30);
    /// <summary>
    /// 我们不希望允许过期时间过低，因为这样锁就无法正常工作（并且默认的最小观察过期时间将大于默认过期时间）
    /// </summary>

    internal static readonly TimeoutValue MinimumExpiry = TimeSpan.FromSeconds(.1);

    private TimeoutValue? _expiry,
        _extensionCadence,
        _minValidityTime,
        _minBusyWaitSleepTime,
        _maxBusyWaitSleepTime;

    internal RedisDistributedSynchronizationOptionsBuilder() { }

    /// <summary>
    /// 指定锁的有效期，不包括自动延长。由于存在自动延长，
    /// 这个值通常对程序行为影响不大。然而，延长有效期意味着
    /// 自动延长请求可以减少频率，节省资源。另一方面，当锁被放弃
    /// 而没有显式释放（例如，如果持有进程崩溃），有效期决定了其他进程
    /// 需要等待多长时间才能获取它。
    /// 
    /// 默认值为30秒。
    /// </summary>

    public RedisDistributedSynchronizationOptionsBuilder Expiry(TimeSpan expiry)
    {
        var expiryTimeoutValue = new TimeoutValue(expiry, nameof(expiry));
        if (expiryTimeoutValue.IsInfinite || expiryTimeoutValue.CompareTo(MinimumExpiry) < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(expiry), expiry, $"Must be >= {MinimumExpiry.TimeSpan} and < ∞");
        }
        this._expiry = expiryTimeoutValue;
        return this;
    }

    /// <summary>
    /// 确定在持有锁期间锁将被扩展的频率。更频繁的扩展意味着更多不必要的请求，
    /// 但也降低了由于进程挂起或在锁过期之前未能及时发出扩展请求而丢失锁的可能性。
    /// 
    /// 默认值为指定的 <see cref="MinValidityTime(TimeSpan)"/> 的1/3。
    /// </summary>

    public RedisDistributedSynchronizationOptionsBuilder ExtensionCadence(TimeSpan extensionCadence)
    {
        this._extensionCadence = new TimeoutValue(extensionCadence, nameof(extensionCadence));
        return this;
    }

    /// <summary>
    /// 确定在持有锁期间锁将被扩展的频率。更频繁的扩展意味着更多不必要的请求，
    /// 但也降低了由于进程挂起或在锁过期之前未能及时发出扩展请求而丢失锁的可能性。
    /// 
    /// 默认值为指定的 <see cref="MinValidityTime(TimeSpan)"/> 的1/3。
    /// </summary>

    public RedisDistributedSynchronizationOptionsBuilder MinValidityTime(TimeSpan minValidityTime)
    {
        var minValidityTimeoutValue = new TimeoutValue(minValidityTime, nameof(minValidityTime));
        if (minValidityTimeoutValue.IsZero)
        {
            throw new ArgumentOutOfRangeException(nameof(minValidityTime), minValidityTime, "may not be zero");
        }
        this._minValidityTime = minValidityTimeoutValue;
        return this;
    }

    /// <summary>
    /// 等待获取锁需要忙等待，交替进行获取尝试和睡眠。
    /// 这决定了在尝试之间睡眠的时间。较低的值会增加竞争时获取请求的数量，但也会提高响应性（等待者注意到竞争锁可用所需的时间）。
    /// 
    /// 指定一个值范围允许实现为每次睡眠在该范围内随机选择一个实际值。这有助于避免两个客户端以一种导致一个客户端垄断锁的方式“同步”的情况。
    /// 
    /// 默认值为[10ms, 800ms]
    /// </summary>

    public RedisDistributedSynchronizationOptionsBuilder BusyWaitSleepTime(TimeSpan min, TimeSpan max)
    {
        var minTimeoutValue = new TimeoutValue(min, nameof(min));
        var maxTimeoutValue = new TimeoutValue(max, nameof(max));

        if (minTimeoutValue.IsInfinite) { throw new ArgumentOutOfRangeException(nameof(min), "may not be infinite"); }
        if (maxTimeoutValue.IsInfinite || maxTimeoutValue.CompareTo(min) < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(max), max, "must be non-infinite and greater than " + nameof(min));
        }

        this._minBusyWaitSleepTime = minTimeoutValue;
        this._maxBusyWaitSleepTime = maxTimeoutValue;
        return this;
    }

    internal static RedisDistributedLockOptions GetOptions(Action<RedisDistributedSynchronizationOptionsBuilder>? optionsBuilder)
    {
        RedisDistributedSynchronizationOptionsBuilder? options;
        if (optionsBuilder != null)
        {
            options = new RedisDistributedSynchronizationOptionsBuilder();
            optionsBuilder(options);
        }
        else
        {
            options = null;
        }

        var expiry = options?._expiry ?? DefaultExpiry;

        TimeoutValue minValidityTime;
        if (options?._minValidityTime is { } specifiedMinValidityTime)
        {
            if (specifiedMinValidityTime.CompareTo(expiry) >= 0)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(minValidityTime),
                    specifiedMinValidityTime.TimeSpan,
                    $"{nameof(minValidityTime)} must be less than {nameof(expiry)} ({expiry.TimeSpan})"
                );
            }
            minValidityTime = specifiedMinValidityTime;
        }
        else
        {
            minValidityTime = TimeSpan.FromMilliseconds(Math.Max(0.9 * expiry.InMilliseconds, 1));
        }

        TimeoutValue extensionCadence;
        if (options?._extensionCadence is { } specifiedExtensionCadence)
        {
            // 注意：我们不允许在这里禁用自动扩展，因为这会导致陷阱
            // 人们可能会放弃句柄，然后由于GC而关闭它。
            // 请参阅此处的讨论：https://github.com/madelson/DistributedLock/issues/130。
            if (specifiedExtensionCadence.CompareTo(minValidityTime) >= 0)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(extensionCadence),
                    specifiedExtensionCadence.TimeSpan,
                    $"{nameof(extensionCadence)} must be less than {nameof(expiry)} ({expiry.TimeSpan})"
                );
            }
            extensionCadence = specifiedExtensionCadence;
        }
        else
        {
            extensionCadence = TimeSpan.FromMilliseconds(minValidityTime.InMilliseconds / 3.0);
        }

        return new RedisDistributedLockOptions(
            redLockTimeouts: new RedLockTimeouts(expiry: expiry, minValidityTime: minValidityTime),
            extensionCadence: extensionCadence,
            minBusyWaitSleepTime: options?._minBusyWaitSleepTime ?? TimeSpan.FromMilliseconds(10),
            maxBusyWaitSleepTime: options?._maxBusyWaitSleepTime ?? TimeSpan.FromSeconds(0.8)
        );
    }
}

internal readonly struct RedisDistributedLockOptions
{
    public RedisDistributedLockOptions(
        RedLockTimeouts redLockTimeouts,
        TimeoutValue extensionCadence,
        TimeoutValue minBusyWaitSleepTime,
        TimeoutValue maxBusyWaitSleepTime)
    {
        this.RedLockTimeouts = redLockTimeouts;
        this.ExtensionCadence = extensionCadence;
        this.MinBusyWaitSleepTime = minBusyWaitSleepTime;
        this.MaxBusyWaitSleepTime = maxBusyWaitSleepTime;
    }

    public RedLockTimeouts RedLockTimeouts { get; }
    public TimeoutValue ExtensionCadence { get; }
    public TimeoutValue MinBusyWaitSleepTime { get; }
    public TimeoutValue MaxBusyWaitSleepTime { get; }
}
